/*

This file is part of Rooms3D.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera)
Visit Esotera at http://www.esoteras3.com
 
Rooms3D is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
Rooms3D is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Rooms3D; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
*/

package com.datasentinel.rooms.j3d.util;

import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Image;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Canvas3D;
import javax.media.j3d.ImageComponent;
import javax.media.j3d.ImageComponent2D;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.border.EmptyBorder;

import com.datasentinel.j3d.utils.geometry.MutableText2D;
import com.datasentinel.rooms.banner.sh3d.BannerPieceFurnitureSupport;
import com.datasentinel.rooms.banner.sh3d.BannerStringPieceOfFurniture;
import com.datasentinel.rooms.file.FileThumbnailChangeListener;
import com.datasentinel.rooms.file.FolderChangeListener;
import com.datasentinel.rooms.file.ThumbnailChangedEvent;
import com.datasentinel.rooms.file.sh3d.FilePieceOfFurnitureTextureManager;
import com.datasentinel.rooms.file.sh3d.HomeFilePieceOfFurniture;
import com.datasentinel.rooms.ui.sh3d.Selectable3D;
import com.sun.j3d.utils.image.TextureLoader;

/**
 * @author rickp
 * 
 */
public class FileThumbnailTextureLoader implements FileThumbnailChangeListener,
		FolderChangeListener
{
	private Logger logger = Logger.getLogger(FileThumbnailTextureLoader.class.getName());
	private int textureLoaderFlags;
	private static final String NON_POWER_OF_TWO_TEXTURES_KEY = "textureNonPowerOfTwoAvailable";
	private List<BranchGroup> toTexture;
	private static Image defaultThumbnail;
	private static ScheduledExecutorService thumbnailLoadService;
	static
	{
		thumbnailLoadService = (ScheduledExecutorService)Executors
				.newSingleThreadScheduledExecutor();
		try
		{
			defaultThumbnail = ImageIO.read(FileThumbnailTextureLoader.class
					.getResource("source_moc.png"));
		}
		catch (IOException ioe)
		{
			ioe.printStackTrace();
		}
	}

	public FileThumbnailTextureLoader(Canvas3D canvas)
	{
		toTexture = Collections.synchronizedList(new ArrayList<BranchGroup>());
		textureLoaderFlags = 0;
//		java.util.Map<?, ?> canvasProperties = canvas.queryProperties();
//		Object textureNonPowerOfTwoAvailable = canvasProperties.get(NON_POWER_OF_TWO_TEXTURES_KEY);
//		if (textureNonPowerOfTwoAvailable instanceof Boolean)
//		{
//			Boolean textureNonPowerOfTwoAvailableBoolean = (Boolean)textureNonPowerOfTwoAvailable;
//			if (textureNonPowerOfTwoAvailableBoolean)
//			{
//				textureLoaderFlags = TextureLoader.ALLOW_NON_POWER_OF_TWO;
//			}
//		}
	}

	public void manageTexture(BranchGroup branchGroup)
	{
		synchronized (toTexture)
		{
			if (branchGroup instanceof Texturable)
			{
				toTexture.add(branchGroup);
				thumbnailLoadService.schedule(new UpdateThumbnail(branchGroup), 50L,
						TimeUnit.MILLISECONDS);
			}
		}
	}

	@Override
	public void fileThumbnailChanged(ThumbnailChangedEvent thumbnailChanged)
	{
		logger.finer("thumbnail changed: "+thumbnailChanged.toString());
		synchronized (toTexture)
		{
			final Image thumbnailImage = thumbnailChanged.getThumbnail();
			final Object eventSource = thumbnailChanged.getSource();
			if (thumbnailImage == null)
			{
				scheduleTextureUpdate((StringBuffer)thumbnailChanged.getData(), eventSource);
			}
			else
			{
				File forWhichThumbnailChanged = (File)thumbnailChanged.getData();
				if(eventSource instanceof Texturable)
				{
					scheduleTextureUpdate(thumbnailImage, eventSource);
				}
				else if(eventSource instanceof FilePieceOfFurnitureTextureManager)
				{
					// check all branches that might match
					logger.warning("source of thumbnail change wasn't branch group");
					synchronized(toTexture)
					{
						for(final BranchGroup texturable: toTexture)
						{
							if(texturable instanceof Texturable)
							{
								Selectable3D homeFilePiece = (Selectable3D) texturable.getUserData();
								if(homeFilePiece.getData().equals(forWhichThumbnailChanged))
								{
									scheduleTextureUpdate(thumbnailImage, texturable);
								}
							}
						}
					}
				}
				else
				{
					logger.severe(String.format("thumbnail change with invalid source [%s]",thumbnailChanged));
				}
			}
		}
	}

	private void scheduleTextureUpdate(final Image thumbnailImage, final Object texturable)
	{
		thumbnailLoadService.schedule(new Runnable() {
			public void run()
			{
				if(texturable instanceof Texturable)
				{
					TextureLoader textureLoader = new TextureLoader(thumbnailImage,
							textureLoaderFlags, null);
					logger.finer("updating texture of "+texturable);
					((Texturable)texturable).applyTexture(textureLoader.getTexture());
				}
			}
		}, 50L, TimeUnit.MILLISECONDS);
	}

	private static JDialog offscreenBuffer;
	private static JLabel label;
	private static Font font;
	private void scheduleTextureUpdate(final StringBuffer textBuffer, final Object texturable)
	{
		thumbnailLoadService.schedule(new Runnable() {
			public void run()
			{
				if (font == null)
					font = new Font("System", Font.PLAIN, 64);
				Dimension d = getPreferredSize(textBuffer);
				int width = d.width;
				int height = d.height;
				d.width = MutableText2D.nearestGreaterPower(width);
				d.height = MutableText2D.nearestGreaterPower(height);
				int x = (d.width - width)>>1;
				int y = (d.height - height)>>1;

				BufferedImage bi = MutableText2D.createImage(d);
				Graphics g = bi.createGraphics();
				g.setFont(font);
				
				try
				{
					Color background = BannerPieceFurnitureSupport.getBackgroundFromHTML(textBuffer.toString());
					if (background != null)
					{
						int dx = Math.min(30, x);
						int dy = Math.min(30, y);
						g.setColor(background);
						g.fillRect(x-dx, y-dy, width+dx+dx, height+dy+dy);
					}
				}
				catch (NumberFormatException e2)
				{
					logger.log(Level.WARNING, "Unparseable body color and transparency flags", e2);
				}

				g.setColor(new Color(0, 0, 0, 255));

				try
				{
					renderOffscreen(textBuffer, g, x, y);
				}
				catch (Exception e1)
				{
					logger.log(Level.SEVERE,
							"Error rendering 2D text in offscreen buffer");
				}
				ImageComponent imageComponent = new ImageComponent2D(
						ImageComponent.FORMAT_RGBA, bi);

				imageComponent.setCapability(ImageComponent.ALLOW_SIZE_READ);

				if(texturable instanceof Texturable)
				{
					logger.finer("updating texture of "+texturable);	
					try
					{
						((Texturable)texturable).applyTexture(MutableText2D.setupTexture(imageComponent)); 
					}
					catch (Throwable e)
					{
						logger.log(Level.SEVERE, "Error applying texture", e);
					}
					finally
					{
						synchronized (textBuffer)
						{
							textBuffer.notifyAll();
						}
					}
				}
			}
		}, 50L, TimeUnit.MILLISECONDS);
	}

	synchronized private void renderOffscreen(final StringBuffer text, Graphics g, int width, int height)
	{
		label.setText(text.toString());
		label.setBorder(new EmptyBorder(height-10, width, 0, 0));
		label.setSize(label.getPreferredSize());
		offscreenBuffer.pack();
		offscreenBuffer.setVisible(true);
		label.paint(g);
		offscreenBuffer.setVisible(false);
	}

	synchronized private Dimension getPreferredSize(StringBuffer text)
	{
		if (offscreenBuffer == null)
		{
			offscreenBuffer = new JDialog();
			label = new JLabel();
			offscreenBuffer.setModal(false);
			label.setFont(font);
			offscreenBuffer.getContentPane().add(label);
			offscreenBuffer.setLocation(Integer.MIN_VALUE, 0);
		}
		label.setBorder(null);
		label.setText(text.toString());
		return label.getPreferredSize();
	}

	@Override
	public void filesAdded(File[] added)
	{
		// not necessary
	}

	@Override
	public void filesModified(File[] modified)
	{
	}

	@Override
	public void filesRemoved(File[] removed)
	{
		for (int i = 0; i < removed.length; i++)
		{
			File toRemove = removed[i];
			synchronized(toTexture)
			{
				BranchGroup[] texturables = toTexture.toArray(new BranchGroup[0]);
				for(final BranchGroup texturable: texturables)
				{
					Object userData = texturable.getUserData();
					if(userData instanceof HomeFilePieceOfFurniture)
					{
						HomeFilePieceOfFurniture homeFilePiece = (HomeFilePieceOfFurniture) userData;
						if(homeFilePiece.getFile().equals(toRemove))
						{
							toTexture.remove(texturable);
						}
					}
				}
			}
		}
	}

	private class UpdateThumbnail implements Runnable
	{
		private BranchGroup branch;

		UpdateThumbnail(BranchGroup newBranch)
		{
			branch = newBranch;
		}

		public void run()
		{
			ThumbnailChangedEvent defaultThumbnailEvent;
			if(branch instanceof Texturable)
			{
				boolean isHomeFilePiece = branch.getUserData() instanceof HomeFilePieceOfFurniture;
				if (isHomeFilePiece)
				{
					HomeFilePieceOfFurniture homePiece = (HomeFilePieceOfFurniture) branch.getUserData();
					defaultThumbnailEvent = new ThumbnailChangedEvent(branch, homePiece.getFile(), defaultThumbnail);
				}
				else if (branch.getUserData() instanceof BannerStringPieceOfFurniture)
				{
					BannerStringPieceOfFurniture homePiece = (BannerStringPieceOfFurniture) branch.getUserData();
					defaultThumbnailEvent = new ThumbnailChangedEvent(branch, homePiece.getData(), null);
				}
				else
					return;
				fileThumbnailChanged(defaultThumbnailEvent);
				if (isHomeFilePiece)
					FilePieceOfFurnitureTextureManager.getInstance().updateThumbnail(branch);
			}
		}
	}
}
